#include <fstream>
#include "gamedatamanager.h"
#include "resourcemanager.h"
#include "strutil.h"
#include "sqlite3helper.h"

using namespace tinyxml2;

GameDataManager *GameDataManager::_instance = nullptr;

GameDataManager::GameDataManager() :
money(0) {
	memset(formations, 0, sizeof(formations));

	SQLite3Helper::init();
}

GameDataManager::~GameDataManager() {
	for (auto f : formations) {
		delete f;
	}

	SQLite3Helper::release();
}

GameDataManager *GameDataManager::getInstance() {
	if (!_instance) {
		_instance = new GameDataManager();
	}

	return _instance;
}

void GameDataManager::init() {
	loadUserInfo();

	loadEquipmentsAttribute();
	loadFormationsFromFiles();
	loadFormationsFromDB();

	loadCampaignsInfo();
	loadCardPackInfo();
	loadEquipmentsQuantity();
}

void GameDataManager::loadEquipmentsAttribute() {
	equipmentsAttribute.insert(make_pair("", EquipmentAttribute()));

	vector<string> *arr[] = {&creatures, &weapons, &armors, &shields, &specials};

	int count = (int)EquipmentType::count;
	for (int i = 0; i < count; i++) {
		arr[i]->clear();
		sprintf(buf, "data/%s.xml", EQUIPMENT_TYPE_NAMES[i]);
		string xml = ResourceManager::loadTextFile(buf);

		tinyxml2::XMLDocument doc;
		doc.Parse(xml.c_str());

		XMLElement *root = doc.FirstChildElement("data");
		XMLElement *item = root->FirstChildElement("i");
		while (item) {
			string name = item->FirstChildElement("name")->GetText();
			strutil::toLowerCase(name);
			strutil::replace(name, " ", "_");

			EquipmentAttribute attr;
			attr.id = atoi(item->FirstChildElement("id")->GetText());
			attr.name = name;
			attr.type = EquipmentAttribute::parseType(item->FirstChildElement("type")->GetText());
			attr.rarity = EquipmentAttribute::parseRarity(item->FirstChildElement("rarity")->GetText());
			attr.genre = EquipmentAttribute::parseGenre(item->FirstChildElement("genre")->GetText());
			attr.attackType = EquipmentAttribute::parseAttackType(item->FirstChildElement("attack_type")->GetText());
			attr.projectileType = EquipmentAttribute::parseBulletProjectileType(item->FirstChildElement("projectile_effect")->GetText());
			attr.effectType = EquipmentAttribute::parseEquipmentEffectType(item->FirstChildElement("other_effect")->GetText());

			parseXmlNode(item, "desc", attr.desc);
			parseXmlNode(item, "range", attr.range);
			parseXmlNode(item, "defense", attr.defense);
			parseXmlNode(item, "points", attr.points);
			parseXmlNode(item, "casting_time", attr.castingTime);
			parseXmlNode(item, "reload_time", attr.reloadTime);

			parseXmlNode(item, "slash_attack", attr.slashAttack);
			parseXmlNode(item, "blunt_attack", attr.bluntAttack);
			parseXmlNode(item, "pierce_attack", attr.piercingAttack);
			parseXmlNode(item, "explosion_attack", attr.explosionAttack);
			parseXmlNode(item, "fire_attack", attr.fireAttack);
			parseXmlNode(item, "ice_attack", attr.iceAttack);
			parseXmlNode(item, "poison_attack", attr.poisonAttack);
			parseXmlNode(item, "thunder_attack", attr.thunderAttack);
			parseXmlNode(item, "dark_attack", attr.darkAttack);
			parseXmlNode(item, "holy_attack", attr.holyAttack);

			parseXmlNode(item, "slash_resist", attr.slashResist);
			parseXmlNode(item, "blunt_resist", attr.bluntResist);
			parseXmlNode(item, "pierce_resist", attr.piercingResist);
			parseXmlNode(item, "explosion_resist", attr.explosionResist);
			parseXmlNode(item, "fire_resist", attr.fireResist);
			parseXmlNode(item, "ice_resist", attr.iceResist);
			parseXmlNode(item, "poison_resist", attr.poisonResist);
			parseXmlNode(item, "thunder_attack", attr.thunderResist);
			parseXmlNode(item, "dark_resist", attr.darkResist);
			parseXmlNode(item, "holy_resist", attr.holyResist);

			XMLElement *xeActions = item->FirstChildElement("actions");
			if (xeActions) {
				XMLElement *xeAction = xeActions->FirstChildElement("i");
				while (xeAction) {
					EquipmentAction action;
					action.actionType = parseEquipmentActionType(xeAction->FirstChildElement("action_type")->GetText());
					action.triggerType = parseEquipmentActionTriggerType(xeAction->FirstChildElement("trigger_type")->GetText());
					action.targetType = parseEquipmentActionTargetType(xeAction->FirstChildElement("target_type")->GetText());
					action.timeMode = parseEquipmentActionTimeMode(xeAction->FirstChildElement("time_mode")->GetText());
					action.modifyProperty = parseEquipmentActionModifyProperty(xeAction->FirstChildElement("modify_property")->GetText());
					action.creature = EquipmentAttribute::parseGenre(xeAction->FirstChildElement("creature")->GetText());
					action.selectionType = parseEquipmentActionSelectionType(xeAction->FirstChildElement("creature_selection")->GetText());

					if(name == "mage_robe") {
						_DHLOG("Mage Robe");
					}

					parseXmlNode(xeAction->FirstChildElement("value"), "value", action.value);
					parseXmlNode(xeAction->FirstChildElement("time"), "time", action.time);

					attr.actions.push_back(action);

					xeAction = xeAction->NextSiblingElement("i");
				}
			}

			arr[i]->push_back(attr.name);
			equipmentsAttribute.insert(make_pair(attr.name, attr));
			equipmentIDMapping.insert(make_pair(attr.id, attr.name));
			equipmentsQuantity.insert(make_pair(attr.name, -1));

			item = item->NextSiblingElement("i");
		}
	}
}

void GameDataManager::loadFormationsFromFiles() {
	string xml = ResourceManager::loadTextFile("data/formations.xml");

	tinyxml2::XMLDocument doc;
	doc.Parse(xml.c_str());

	int index = 0;

	XMLElement *root = doc.FirstChildElement("data");
	XMLElement *item = root->FirstChildElement("f");
	while (item) {
		FormationInfo *f = new FormationInfo();
		if (formations[index]) {
			delete formations[index];
		}
		formations[index++] = f;

		f->name = item->Attribute("name");
		strutil::toLowerCase(f->name);

		XMLElement *xeUnits = item->FirstChildElement("units");
		XMLElement *xeUnit = xeUnits->FirstChildElement("i");
		while (xeUnit) {
			UnitInfo info;

			info.name = xeUnit->Attribute("k");
			info.id = xeUnit->IntAttribute("id");
			parseXmlNode(xeUnit, "creature", info.creature);
			parseXmlNode(xeUnit, "weapon", info.weapon);
			parseXmlNode(xeUnit, "armor", info.armor);
			parseXmlNode(xeUnit, "shield", info.shield);
			parseXmlNode(xeUnit, "special", info.special);

			f->units.push_back(info);
			xeUnit = xeUnit->NextSiblingElement("i");
		}

		XMLElement *xeOrders = item->FirstChildElement("orders");
		if (xeOrders) {
			int squadIndex = 0;
			XMLElement *xeOrderItem = xeOrders->FirstChildElement("i");
			while (xeOrderItem) {
				f->orders[squadIndex++] = parseSquadOrder(xeOrderItem->GetText());
				xeOrderItem = xeOrderItem->NextSiblingElement("i");
			}
		}

		XMLElement *xeGrids = item->FirstChildElement("grids");
		if (xeGrids) {
			XMLElement *xeGridItem = xeGrids->FirstChildElement("i");
			while (xeGridItem) {
				int lineIndex = xeGridItem->IntAttribute("line");
				int rowIndex = xeGridItem->IntAttribute("row");
				f->grids[lineIndex][rowIndex] = xeGridItem->Attribute("k");
				xeGridItem = xeGridItem->NextSiblingElement("i");
			}
		}

		item = item->NextSiblingElement("f");
	}
}

void GameDataManager::parseXmlNode(tinyxml2::XMLElement *xe, const char *tag, int &v) {
	auto node = xe->FirstChildElement(tag);
	if(node) {
		const char *s = node->GetText();
		if (s) {
			v = atoi(s);
		}
	}
}

void GameDataManager::parseXmlNode(tinyxml2::XMLElement *xe, const char *tag, float &v) {
	auto node = xe->FirstChildElement(tag);
	if(node) {
		const char *s = node->GetText();
		if (s) {
			v = static_cast<float>(atof(s));
		}
	}
}

void GameDataManager::parseXmlNode(tinyxml2::XMLElement *xe, const char *tag, string &v) {
	auto node = xe->FirstChildElement(tag);
	if (node) {
		const char *s = node->GetText();
		if (s) {
			v = s;
		}
	}
}

SquadOrder GameDataManager::parseSquadOrder(char const *s) {
	SquadOrder ret = SquadOrder::hold;

	string order = s;
	strutil::toLowerCase(order);

	if (order == "fast") {
		ret = SquadOrder::fast;
	} else if (order == "slow") {
		ret = SquadOrder::slow;
	}

	return ret;
}

EquipmentActionType GameDataManager::parseEquipmentActionType(const char *s) {
	const char *arr[] = {
			"modifier",
			"summon",
	};

	EquipmentActionType ret = EquipmentActionType::count;

	for (int i = 0; i < (int) EquipmentActionType::count; i++) {
		if (s && strcmp(arr[i], s) == 0) {
			ret = (EquipmentActionType) i;
		}
	}

	return ret;
};

EquipmentActionTriggerType GameDataManager::parseEquipmentActionTriggerType(const char *s) {
	const char *arr[] = {
			"on_begin",
			"on_attack",
			"on_kill",
			"on_death",
			"on_raise",
	};

	EquipmentActionTriggerType ret = EquipmentActionTriggerType::count;

	for (int i = 0; i < (int) EquipmentActionTriggerType::count; i++) {
		if (s && strcmp(arr[i], s) == 0) {
			ret = (EquipmentActionTriggerType) i;
		}
	}

	return ret;
};

EquipmentActionTargetType GameDataManager::parseEquipmentActionTargetType(const char *s) {
	const char *arr[] = {
			"wearer",
			"front_units",
			"melee_units",
			"all_units",
			"around_units",
			"squad_units",
			"our_units",
			"front_line_enemy",
			"all_enemy",
	};

	EquipmentActionTargetType ret = EquipmentActionTargetType::count;

	for (int i = 0; i < (int) EquipmentActionTargetType::count; i++) {
		if (s && strcmp(arr[i], s) == 0) {
			ret = (EquipmentActionTargetType) i;
		}
	}

	return ret;
}

EquipmentActionTimeMode GameDataManager::parseEquipmentActionTimeMode(const char *s) {
	const char *arr[] = {
			"instant",
			"delay",
			"repeat",
	};

	EquipmentActionTimeMode ret = EquipmentActionTimeMode::count;

	for (int i = 0; i < (int) EquipmentActionTimeMode::count; i++) {
		if (s && strcmp(arr[i], s) == 0) {
			ret = (EquipmentActionTimeMode) i;
		}
	}

	return ret;
};

EquipmentActionModifyProperty GameDataManager::parseEquipmentActionModifyProperty(const char *s) {
	const char *arr[] = {
			"defense",
			"reload_time",
			"casting_time",
			"range",
			"slash_attack",
			"blunt_attack",
			"piercing_attack",
			"explosion_attack",
			"fire_attack",
			"ice_attack",
			"poison_attack",
			"thunder_attack",
			"dark_attack",
			"holy_attack",
			"slash_resist",
			"blunt_resist",
			"piercing_resist",
			"explosion_resist",
			"fire_resist",
			"ice_resist",
			"poison_resist",
			"thunder_resist",
			"dark_resist",
			"holy_resist",
	};

	EquipmentActionModifyProperty ret = EquipmentActionModifyProperty::count;

	for (int i = 0; i < (int) EquipmentActionModifyProperty::count; i++) {
		if (s && strcmp(arr[i], s) == 0) {
			ret = (EquipmentActionModifyProperty) i;
		}
	}

	return ret;
};

EquipmentActionSelectionType GameDataManager::parseEquipmentActionSelectionType(const char *s) {
	const char *arr[] = {"all", "specified", "other",};

	EquipmentActionSelectionType ret = EquipmentActionSelectionType::count;

	for (int i = 0; i < (int) EquipmentActionSelectionType::count; i++) {
		if (s && strcmp(arr[i], s) == 0) {
			ret = (EquipmentActionSelectionType) i;
		}
	}

	return ret;
}

string GameDataManager::getEquipmentName(EquipmentType type, int index) {
	switch (type) {
		case EquipmentType::creature:
			return creatures[index];
		case EquipmentType::weapon:
			return weapons[index];
		case EquipmentType::armor:
			return armors[index];
		case EquipmentType::shield:
			return shields[index];
		case EquipmentType::special:
			return specials[index];
		default:
			break;
	}

	return "";
}

string GameDataManager::getEquipmentIconFileName(EquipmentType type, int index) {
	
	string name = getEquipmentName(type, index);
	if (type == EquipmentType::weapon || type == EquipmentType::shield) {
		sprintf(buf, "%s_icon/%s_icon.png", EQUIPMENT_TYPE_NAMES[(int) type], name.c_str());
	} else {
		sprintf(buf, "%s_icon/%s.png", EQUIPMENT_TYPE_NAMES[(int) type], name.c_str());
	}
	name = buf;

	return name;
}

string GameDataManager::getEquipmentIconFileName(EquipmentType type, const string &name) {
	
	if (type == EquipmentType::weapon || type == EquipmentType::shield) {
		sprintf(buf, "%s_icon/%s_icon.png", EQUIPMENT_TYPE_NAMES[(int) type], name.c_str());
	} else {
		sprintf(buf, "%s_icon/%s.png", EQUIPMENT_TYPE_NAMES[(int) type], name.c_str());
	}
	return buf;
}

void GameDataManager::loadFormationsFromDB() {
	for (int i = 0; i < FORMATION_COUNT; i++) {
		sprintf(buf, "formation_%d", i);
		int n = 0;
		const char *p = SQLite3Helper::getBlob(buf, n);
		if (n >= 149 && p) {
			*(formations[i]) = FormationInfo::fromBytes(p);
		} else {
			_DHLOG("invalid formation data loaded from db");
		}
	}
}

void GameDataManager::saveFormation(int index, const FormationInfo &formationInfo) {
	*(formations[index]) = formationInfo;

	sprintf(buf, "formation_%d", index);

	const char *p = formationInfo.toBytes();
	SQLite3Helper::setBlob(buf, p, formationInfo.getChunkByteCount());
	delete p;
}

FormationInfo *GameDataManager::loadCampaignFormation(int episodeIndex, int chapterIndex) {
	FormationInfo *f = nullptr;

	
	sprintf(buf, "data/campaign/%d_%d.xml", episodeIndex + 1, chapterIndex + 1);
	string xml = ResourceManager::loadTextFile(buf);

	tinyxml2::XMLDocument doc;
	doc.Parse(xml.c_str());

	XMLElement *root = doc.FirstChildElement("data");
	XMLElement *item = root->FirstChildElement("f");
	if (item) {
		f = new FormationInfo();

		XMLElement *xeUnits = item->FirstChildElement("units");
		XMLElement *xeUnit = xeUnits->FirstChildElement("i");
		while (xeUnit) {
			UnitInfo info;

			info.name = xeUnit->Attribute("k");
			info.id = xeUnit->IntAttribute("id");
			parseXmlNode(xeUnit, "creature", info.creature);
			parseXmlNode(xeUnit, "weapon", info.weapon);
			parseXmlNode(xeUnit, "armor", info.armor);
			parseXmlNode(xeUnit, "shield", info.shield);
			parseXmlNode(xeUnit, "special", info.special);

			f->units.push_back(info);
			xeUnit = xeUnit->NextSiblingElement("i");
		}

		XMLElement *xeOrders = item->FirstChildElement("orders");
		if (xeOrders) {
			int squadIndex = 0;
			XMLElement *xeOrderItem = xeOrders->FirstChildElement("i");
			while (xeOrderItem) {
				f->orders[squadIndex++] = parseSquadOrder(xeOrderItem->GetText());
				xeOrderItem = xeOrderItem->NextSiblingElement("i");
			}
		}

		XMLElement *xeGrids = item->FirstChildElement("grids");
		if (xeGrids) {
			XMLElement *xeGridItem = xeGrids->FirstChildElement("i");
			while (xeGridItem) {
				int lineIndex = xeGridItem->IntAttribute("line");
				int rowIndex = xeGridItem->IntAttribute("row");
				f->grids[lineIndex][rowIndex] = xeGridItem->Attribute("k");
				xeGridItem = xeGridItem->NextSiblingElement("i");
			}
		}
	}

	return f;
}

void GameDataManager::loadCampaignsInfo() {
	campaigns.clear();

	CampaignInfo info1;
	info1.chapters.assign(9, ChapterInfo());
	campaigns.push_back(info1);

	CampaignInfo info2;
	info2.chapters.assign(10, ChapterInfo());
	campaigns.push_back(info2);

	CampaignInfo info3;
	info3.chapters.assign(10, ChapterInfo());
	campaigns.push_back(info3);

	string xml = ResourceManager::loadTextFile("data/campaigns.xml");

	tinyxml2::XMLDocument doc;
	doc.Parse(xml.c_str());

	XMLElement *root = doc.FirstChildElement("data");
	XMLElement *item = root->FirstChildElement("i");
	while (item) {
		string k = item->Attribute("k");
		vector<string> arr = strutil::split(k, "_");
		int index = atoi(arr[0].c_str());

		CampaignInfo &info = campaigns[index - 1];
		if (arr[1] == "end") {
			parseXmlNode(item, "text", info.closingRemarks);
		} else {
			int chapterIndex = atoi(arr[1].c_str());
			ChapterInfo &chapter = info.chapters[chapterIndex - 1];
			chapter.index = chapterIndex;
			parseXmlNode(item, "title", chapter.title);
			parseXmlNode(item, "text", chapter.text);
		}

		item = item->NextSiblingElement("i");
	}
}

void GameDataManager::loadCardPackInfo() {
	packs.clear();

	string xml = ResourceManager::loadTextFile("data/packs.xml");

	tinyxml2::XMLDocument doc;
	doc.Parse(xml.c_str());

	XMLElement *root = doc.FirstChildElement("data");
	XMLElement *item = root->FirstChildElement("i");
	while (item) {
		CardPackInfo info;

		string type;
		parseXmlNode(item, "type", type);
		info.type = CardPackInfo::parseType(type.c_str());

		parseXmlNode(item, "title", info.title);
		parseXmlNode(item, "price", info.price);
		parseXmlNode(item, "legendary", info.legendary);
		parseXmlNode(item, "rare", info.rare);
		parseXmlNode(item, "uncommon", info.uncommon);
		parseXmlNode(item, "common", info.common);

		packs.push_back(info);

		item = item->NextSiblingElement("i");
	}
}

void GameDataManager::loadUserInfo() {
	const char *s = SQLite3Helper::get("device_id");
	if (s) {
		deviceID = s;
	}

	s = SQLite3Helper::get("player_name");
	if (s) {
		playerName = s;
	}

	s = SQLite3Helper::get("friends");
	if (s) {
		friends = strutil::split(s, "\n");
	}

	money = SQLite3Helper::getInt("money");
}

void GameDataManager::loadEquipmentsQuantity() {
	
	for (auto item : equipmentIDMapping) {
		sprintf(buf, "%s_quantity", item.second.c_str());
		equipmentsQuantity[item.second] = SQLite3Helper::getInt(buf);
	}
}

vector<string> GameDataManager::getEquipmentsNames(EquipmentType type, EquipmentRarity rarity) {
	vector<string> ret;

	const vector<string> *arr[] = {&creatures, &weapons, &armors, &shields, &specials};
	if (rarity == EquipmentRarity::count) {
		ret = *(arr[(int) type]);
	} else {
		for (string s : *(arr[(int) type])) {
			if (equipmentsAttribute[s].rarity == rarity) {
				ret.push_back(s);
			}
		}
	}

	return ret;
}

void GameDataManager::saveMoney() {
	SQLite3Helper::setInt("money", money);
}

DamageType GameDataManager::getWeaponDamageType(BulletProjectileType type) {
	static const DamageType types[] = {
			DamageType::count,
			DamageType::piercing,
			DamageType::piercing,
			DamageType::explosion,
			DamageType::explosion,
			DamageType::fire,
			DamageType::explosion,
			DamageType::explosion,
			DamageType::blunt,
			DamageType::poison,
			DamageType::explosion,
			DamageType::fire,
			DamageType::dark,
			DamageType::fire,
			DamageType::fire,
			DamageType::ice,
			DamageType::holy,
			DamageType::ice,
			DamageType::ice,
			DamageType::thunder,
			DamageType::count,
			DamageType::count,
			DamageType::poison,
			DamageType::poison,
			DamageType::fire,
			DamageType::thunder,
			DamageType::thunder,
			DamageType::poison,
			DamageType::count,
			DamageType::dark,
	};

	return types[(int) type];
}
